package com.bugsnag.ohos;

import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.net.ConnectionProperties;
import ohos.net.NetCapabilities;
import ohos.net.NetHandle;
import ohos.net.NetManager;
import ohos.net.NetStatusCallback;
import ohos.rpc.RemoteException;

/**
 * NetworkChangeCallback
 *
 * @since 2021-07-26
 */
interface NetworkChangeCallback {
    /**
     * invoke
     *
     * @param hasConnection
     * @param networkState
     */
    void invoke(boolean hasConnection, String networkState);
}

/**
 * ConnectivityCompat
 *
 * @since 2021-07-26
 */
public class ConnectivityCompat implements Connectivity {
    private NetManager nm;
    private Connectivity connectivity;

    /**
     * constructor
     *
     * @param context
     * @param callback
     */
    public ConnectivityCompat(Context context, NetworkChangeCallback callback) {
        nm = ContextExtensions.getNetManagerFrom(context);
        if (nm == null) {
            connectivity = new UnknownConnectivity();
        } else {
            connectivity = new ConnectivityLegacy(context, callback);
        }
    }

    @Override
    public void registerForNetworkChanges() {
        connectivity.registerForNetworkChanges();
    }

    @Override
    public void unregisterForNetworkChanges() {
        connectivity.unregisterForNetworkChanges();
    }

    @Override
    public boolean hasNetworkConnection() {
        return connectivity.hasNetworkConnection();
    }

    @Override
    public String retrieveNetworkAccessState() {
        return connectivity.retrieveNetworkAccessState();
    }
}

/**
 * ConnectivityLegacy
 *
 * @since 2021-07-26
 */
class ConnectivityLegacy implements Connectivity {
    /**
     * connectivity action
     */
    public static final String CONNECTIVITY_ACTION = "connectivity.action";
    private Context context;
    private ConnectivityChangeReceiver changeReceiver;
    private NetManager nm;

    private class DefaultNetStatusCallback extends NetStatusCallback {
        @Override
        public void onAvailable(NetHandle handle) {
            super.onAvailable(handle);
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }

        @Override
        public void onBlockedStatusChanged(NetHandle handle, boolean blocked) {
            super.onBlockedStatusChanged(handle, blocked);
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }

        @Override
        public void onLosing(NetHandle handle, long maxMsToLive) {
            super.onLosing(handle, maxMsToLive);
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }

        @Override
        public void onLost(NetHandle handle) {
            super.onLost(handle);
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }

        @Override
        public void onUnavailable() {
            super.onUnavailable();
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }

        @Override
        public void onCapabilitiesChanged(NetHandle handle, NetCapabilities networkCapabilities) {
            super.onCapabilitiesChanged(handle, networkCapabilities);
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }

        @Override
        public void onConnectionPropertiesChanged(NetHandle handle, ConnectionProperties connectionProperties) {
            super.onConnectionPropertiesChanged(handle, connectionProperties);
            Intent intent = new Intent();
            Intent.OperationBuilder operationBuilder = new Intent.OperationBuilder();
            Operation operation = operationBuilder.withAction(CONNECTIVITY_ACTION).build();
            intent.setOperation(operation);
            CommonEventData commonEventData = new CommonEventData(intent);
            try {
                CommonEventManager.publishCommonEvent(commonEventData);
            } catch (RemoteException e) {
                e.getMessage();
            }
        }
    }

    ConnectivityLegacy(Context context, NetworkChangeCallback callback) {
        this.context = context;
        MatchingSkills matchingSkills = new MatchingSkills();
        matchingSkills.addEvent(CONNECTIVITY_ACTION);
        CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
        this.changeReceiver = new ConnectivityChangeReceiver(subscribeInfo, callback);
        this.nm = NetManager.getInstance(context);
        nm.addDefaultNetStatusCallback(new DefaultNetStatusCallback());
    }

    @Override
    public void registerForNetworkChanges() {
        ContextExtensions.registerReceiverSafe(changeReceiver, null);
    }

    @Override
    public void unregisterForNetworkChanges() {
        ContextExtensions.unregisterReceiverSafe(changeReceiver, null);
    }

    @Override
    public boolean hasNetworkConnection() {
        NetCapabilities netCapabilities = nm.getNetCapabilities(nm.getDefaultNet());
        return netCapabilities.hasCap(NetCapabilities.NET_CAPABILITY_VALIDATED);
    }

    @Override
    public String retrieveNetworkAccessState() {
        if (isWifiConnectedInternet(context)) {
            return "wifi";
        } else {
            return "cellular";
        }
    }

    /**
     * ConnectivityChangeReceiver
     *
     * @since 2021-07-26
     */
    private class ConnectivityChangeReceiver extends CommonEventSubscriber {
        private NetworkChangeCallback cb;

        /**
         * constructor
         *
         * @param subscribeInfo
         * @param cb
         */
        ConnectivityChangeReceiver(CommonEventSubscribeInfo subscribeInfo, NetworkChangeCallback cb) {
            super(subscribeInfo);
            this.cb = cb;
        }

        @Override
        public void onReceiveEvent(CommonEventData commonEventData) {
            if (cb != null) {
                cb.invoke(hasNetworkConnection(), retrieveNetworkAccessState());
            }
        }
    }

    /**
     * 是否是WiFi连接
     *
     * @param ctx 上下文
     * @return true表示WiFi连接，并且可以上网。false表示WiFi没连接，或者WiFi连接了，但是不能上网
     */
    public boolean isWifiConnectedInternet(Context ctx) {
        NetManager netManager = NetManager.getInstance(ctx);
        NetCapabilities netCapabilities = netManager.getNetCapabilities(netManager.getDefaultNet());
        return netCapabilities.hasCap(NetCapabilities.NET_CAPABILITY_VALIDATED)
            && netCapabilities.hasBearer(NetCapabilities.BEARER_WIFI)
            || netCapabilities.hasBearer(NetCapabilities.BEARER_WIFI_AWARE);
    }

    /**
     * 是否是数据网络连接
     *
     * @param ctx 上下文
     * @return true表示数据网络连接，并且可以上网。false表示数据网络没连接，或者数据网络连接了，但是不能上网
     */
    public boolean isMobileConnectedInternet(Context ctx) {
        NetManager netManager = NetManager.getInstance(ctx);
        NetCapabilities netCapabilities = netManager.getNetCapabilities(netManager.getDefaultNet());
        return netCapabilities.hasCap(NetCapabilities.NET_CAPABILITY_VALIDATED)
            && netCapabilities.hasBearer(NetCapabilities.BEARER_CELLULAR);
    }
}

/**
 * Connectivity used in cases where we cannot access the system ConnectivityManager.
 * We assume that there is some sort of network and do not attempt to report any network changes.
 */
class UnknownConnectivity implements Connectivity {
    @Override
    public void registerForNetworkChanges() {
    }

    @Override
    public void unregisterForNetworkChanges() {
    }

    @Override
    public boolean hasNetworkConnection() {
        return true;
    }

    @Override
    public String retrieveNetworkAccessState() {
        return "unknown";
    }
}
